關於 xv6 的環境架設,可以參考我之前寫的這篇文章
6.S082 課程連結(我這裡用的是 2021 的版本)
這篇文章是要寫課程當中的 Lab syscall
1. 開始寫題目前應該要先做的幾件事
2. 題目叫我們閱讀的程式碼
user/user.h, user/usys.pl
kernel/syscall.h, kernel/syscall.c
kernel/proc.h, kernel/proc.c
3. 程式實作
4. 參考資料
先把他看完對於寫 lab 會有幫助的
如果你看的是 2020 的版本,可以直接點這個連結
但如果你看的是 2021 的版本了話,要點選這個 github 頁面 下載程式碼並且自己 make 出來才行
我個人覺得 xv6 book 寫的不是那麼平易近人,很多時候他假設了讀者有一定的基礎知識,
所以我是建議在書中看到了什麼不懂的名詞,就先去搞懂後再回來看會比較好,
像是 page table, virtual memory, risc-v 的架構,一些 C 語言語法等等,
雖然這樣感好像很花時間,但反正在看其他的東西本身也是一種學習,
在 Lab syscall 要我們先讀 chapter 2, 4.3, 4.4,
就先把他們讀完再繼續吧,讀的時候也要搭配 source code 會比要好懂
user/user.h, user/usys.pluser/user.h,是個 system call 的 prototype
/*.........*/
// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
/*.........*/
但就以 int chdir(const char*) 為例,在 kernel 中,他並不會真的有
int chdir(const char*)
{
//***........***//
}
這種實作方式
而是在 make qemu 之後,Makefile 會執行 user/usys.pl 這個腳本程式,
他會製造出 user/usys.S 這個 risc-v 組語:
#include "kernel/syscall.h"
.global fork
fork:
li a7, SYS_fork
ecall
ret
.global exit
exit:
li a7, SYS_exit
ecall
ret
...
也就是在 user program 中,叫到了 fork() 這個 function 之後,他會跳到 fork: 這個 flag(symbol) 這裡來繼續執行
如果 user program 呼叫了 fork()這個 function 會執行以下兩件事:
li (load immediate):把 fork 的編號 SYS_fork (定義在 kernel/syscall.h) 放進 register a7 裡ecall:從 user mode 進入到 supervisor mode (實際上就是改變了 CPU chip 裡的一個 flag)並且進入到了 kernel/syscall.c 的 syscall()syscall() 會根據 register a7 的 system call 編號來幫你把 program counter 指向 system call 真的實做的 function(在 kernel/sysfile.c 或是 kernel/sysproc.c 等等)
這麼做的原因在於,system call 是個權限較高的行為,所以處理起來要比較小心才行
user program 只能把呼叫的 system call 編號放在 register a7 然後由 syscall() 來決定這個 user program 是否有資格執行這個 system call
kernel/syscall.h, kernel/syscall.c像前面有提到的,kernel/syscall.h 定義著 system call 的編號:
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
#define SYS_pipe 4
#define SYS_read 5
#define SYS_kill 6
#define SYS_exec 7
#define SYS_fstat 8
#define SYS_chdir 9
#define SYS_dup 10
#define SYS_getpid 11
#define SYS_sbrk 12
#define SYS_sleep 13
#define SYS_uptime 14
#define SYS_open 15
#define SYS_write 16
#define SYS_mknod 17
#define SYS_unlink 18
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
kernel/syscall.c 則定義著一些執行 system call 所需要用的 function ,
例如如何拿取 user program 傳來的參數等等,這些在 xv6 book 寫的很清楚
kernel/proc.h, kernel/proc.c在 kernel 中,紀錄著各個 process 的資訊,而他使用的資料結構就是 kernel/proc.h 的 struct proc
這個 struct 值得多加注意一下
依照 Lab system call 的 Some hints 的指示,一步一步的做下去就好
而這個 lab 已經提供了 user program user/trace.c 但是這個 program 呼叫到的 system call trace(int) 還沒有被實作出來
我們的目的就是要把他實作出來才行
Makefile 中,加入 $U/_trace
user/user.h:// user/user.h
...
int trace(int);
...
user/usys.pl:...
entry("trace");
kernel/syscall.h
...
#define SYS_trace 22
到了這裡,已經可以用 make qemu compile 成功了,
只不過 trace 這個 system call 還是還沒實作出來,
目前只解決了以 user program 而言的 link 上的問題
kernel/proc.h 中,增加一個 variablestruct proc {
...
uint trace_mask;
};
kernel/sysproc.c 實作出 sys_trace()
...
uint64
sys_trace(void)
{
int n;
if(argint(0, &n) < 0)
return -1;
struct proc *p = myproc();
p->trace_mask = n;
return 0;
}
其實就只是把 proc 中的 trace_mask 給設為 user program 傳過來的參數而已
但業就是因為他改變了 kernel 中的資訊,所以需要用更高等級的權限(supervisor mode)才可以
kernel/syscall.c 加入extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
...
[SYS_trace] sys_trace,
};
kernel/proc.c 中的 fork() fork 出新的 process 時,也要把 trace_mask 也複製過去才行int
fork(void)
{
...
// copy trace_mask
np->trace_mask = p->trace_mask;
...
}
kernel/syscall.c 的 syscall() 要在 system call return 時,判斷要不要 print 出資訊static char *syscall_names[] = {
[SYS_fork] "fork",
[SYS_exit] "exit",
[SYS_wait] "wait",
[SYS_pipe] "pipe",
[SYS_read] "read",
[SYS_kill] "kill",
[SYS_exec] "exec",
[SYS_fstat] "fstat",
[SYS_chdir] "chdir",
[SYS_dup] "dup",
[SYS_getpid] "getpid",
[SYS_sbrk] "sbrk",
[SYS_sleep] "sleep",
[SYS_uptime] "uptime",
[SYS_open] "open",
[SYS_write] "write",
[SYS_mknod] "mknod",
[SYS_unlink] "unlink",
[SYS_link] "link",
[SYS_mkdir] "mkdir",
[SYS_close] "close",
[SYS_trace] "trace",
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();
if ((1 << num) & p->trace_mask)
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}